/**
 * form 表单组件
 */

import { layui } from '../core/layui.js';
import { lay } from '../core/lay.js';
import { i18n } from '../core/i18n.js';
import $ from 'jquery';
import { util } from './util.js';
import { layer } from './layer.js';

var hint = layui.hint();
// var device = layui.device();

var MOD_NAME = 'form';
var ELEM = '.layui-form';
var THIS = 'layui-this';
// var SHOW = 'layui-show';
var HIDE = 'layui-hide';
var DISABLED = 'layui-disabled';
var OUT_OF_RANGE = 'layui-input-number-out-of-range';
var BAD_INPUT = 'layui-input-number-invalid';

var resizeObserver = lay.createSharedResizeObserver(MOD_NAME);

// ie8 中可以获取到 input 元素的 'indeterminate' 属性描述符，但重新定义 getter/setter 无效，无报错
// AppleWebKit/537.36 无法获取 input 元素任意属性的属性描述符(包括lookupGetter)，但可以重新定义 getter/setter
var needCheckboxFallback =
  (lay.ie && parseFloat(lay.ie) === 8) ||
  typeof Object.getOwnPropertyDescriptor(
    HTMLInputElement.prototype,
    'checked',
  ) === 'undefined';

var Form = function () {
  this.config = {
    // 内置的验证规则
    verify: {
      required: function (value) {
        if (!/[\S]+/.test(value) || value === undefined || value === null) {
          return i18n.$t('form.validateMessages.required');
        }
      },
      phone: function (value) {
        var EXP = /^1\d{10}$/;
        if (value && !EXP.test(value)) {
          return i18n.$t('form.validateMessages.phone');
        }
      },
      email: function (value) {
        var EXP = /^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
        if (value && !EXP.test(value)) {
          return i18n.$t('form.validateMessages.email');
        }
      },
      url: function (value) {
        var EXP = /^(#|(http(s?)):\/\/|\/\/)[^\s]+\.[^\s]+$/;
        if (value && !EXP.test(value)) {
          return i18n.$t('form.validateMessages.url');
        }
      },
      number: function (value) {
        if (value && isNaN(value)) {
          return i18n.$t('form.validateMessages.number');
        }
      },
      date: function (value) {
        var EXP =
          /^(\d{4})[-/](\d{1}|0\d{1}|1[0-2])([-/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/;
        if (value && !EXP.test(value)) {
          return i18n.$t('form.validateMessages.date');
        }
      },
      identity: function (value) {
        var EXP = /(^\d{15}$)|(^\d{17}(x|X|\d)$)/;
        if (value && !EXP.test(value)) {
          return i18n.$t('form.validateMessages.identity');
        }
      },
    },
    autocomplete: null, // 全局 autocomplete 状态。 null 表示不干预
  };
};

// 全局设置
Form.prototype.set = function (options) {
  var that = this;
  $.extend(true, that.config, options);
  return that;
};

// 验证规则设定
Form.prototype.verify = function (settings) {
  var that = this;
  $.extend(true, that.config.verify, settings);
  return that;
};

// 获取指定表单对象
Form.prototype.getFormElem = function (filter) {
  return $(
    ELEM +
      (function () {
        return filter ? '[lay-filter="' + filter + '"]' : '';
      })(),
  );
};

// 表单事件
Form.prototype.on = function (events, callback) {
  return layui.onevent.call(this, MOD_NAME, events, callback);
};

// 赋值/取值
Form.prototype.val = function (filter, object) {
  var that = this;
  var formElem = that.getFormElem(filter);

  // 遍历
  formElem.each(function () {
    var itemForm = $(this);

    // 赋值
    for (var key in object) {
      if (!lay.hasOwn(object, key)) continue;

      var type;
      var value = object[key];
      var itemElem = itemForm.find('[name="' + key + '"]');

      // 如果对应的表单不存在，则不执行
      if (!itemElem[0]) continue;
      type = itemElem[0].type;

      // 如果为复选框
      if (type === 'checkbox') {
        itemElem[0].checked = value;
      } else if (type === 'radio') {
        // 如果为单选框
        itemElem.each(function () {
          this.checked = this.value == value + '';
        });
      } else {
        // 其它类型的表单
        itemElem.val(value);
      }
    }
  });

  form.render(null, filter);

  // 返回值
  return that.getValue(filter);
};

// 取值
Form.prototype.getValue = function (filter, itemForm) {
  itemForm = itemForm || this.getFormElem(filter);

  var nameIndex = {}, // 数组 name 索引
    field = {},
    fieldElem = itemForm.find('input,select,textarea'); // 获取所有表单域

  layui.each(fieldElem, function (_, item) {
    var othis = $(this),
      init_name; // 初始 name

    item.name = (item.name || '').replace(/^\s*|\s*&/, '');
    if (!item.name) return;

    // 用于支持数组 name
    if (/^.*\[\]$/.test(item.name)) {
      var key = item.name.match(/^(.*)\[\]$/g)[0];
      nameIndex[key] = nameIndex[key] | 0;
      init_name = item.name.replace(
        /^(.*)\[\]$/,
        '$1[' + nameIndex[key]++ + ']',
      );
    }

    if (/^(checkbox|radio)$/.test(item.type) && !item.checked) return; // 复选框和单选框未选中，不记录字段
    // select 多选用 jQuery 方式取值，未选中 option 时，
    // jQuery v2.2.4 及以下版本返回 null，以上(3.x) 返回 []。
    // 统一规范化为 []，参考 https://github.com/jquery/jquery/issues/2562
    field[init_name || item.name] =
      this.tagName === 'SELECT' &&
      typeof this.getAttribute('multiple') === 'string'
        ? othis.val() || []
        : this.value;
  });

  return field;
};

// 表单控件渲染
Form.prototype.render = function (type, filter) {
  var that = this;
  var options = that.config;
  var elemForm = $(
    ELEM +
      (function () {
        return filter ? '[lay-filter="' + filter + '"]' : '';
      })(),
  );
  var items = {
    // 输入框
    input: function (elem) {
      var inputs = elem || elemForm.find('input,textarea');

      // 初始化全局的 autocomplete
      options.autocomplete && inputs.attr('autocomplete', options.autocomplete);

      var handleInputNumber = function (elem, eventType) {
        var that = this;
        var rawValue = elem.val();
        var value = Number(rawValue);
        var step = Number(elem.attr('step')) || 1; // 加减的数字间隔
        var min = Number(elem.attr('min'));
        var max = Number(elem.attr('max'));
        var precision = Number(elem.attr('lay-precision'));
        var noAction = eventType !== 'click' && rawValue === ''; // 初始渲染和失焦时空值不作处理
        var isInit = eventType === 'init';
        var isBadInput = isNaN(value);
        var isStepStrictly = typeof elem.attr('lay-step-strictly') === 'string';

        elem.toggleClass(BAD_INPUT, isBadInput);
        if (isBadInput) return; // 若非数字，则不作处理

        if (eventType === 'click') {
          // 兼容旧版行为，2.10 以前 readonly 不禁用控制按钮
          if (
            elem[0].type === 'text' &&
            typeof elem.attr('readonly') === 'string'
          )
            return;
          var isDecrement = !!$(that).index(); // 0: icon-up, 1: icon-down
          value = isDecrement ? value - step : value + step;
        }

        // 获取小数点后位数
        var decimals = function (step) {
          var decimals = (step.toString().match(/\.(\d+$)/) || [])[1] || '';
          return decimals.length;
        };

        precision =
          precision >= 0
            ? precision
            : Math.max(decimals(step), decimals(rawValue));

        // 赋值
        if (!noAction) {
          // 初始渲染时只处理数字精度
          if (!isInit) {
            if (isStepStrictly) {
              value = Math.round(value / step) * step;
            }
            if (value <= min) value = min;
            if (value >= max) value = max;
          }
          // 若 `lay-precision` 为 0, 则表示只保留整数
          if (precision === 0) {
            value = parseInt(value);
          } else if (precision > 0) {
            // 小数位精度
            value = value.toFixed(precision);
          }

          elem.val(value);
          elem.attr('lay-input-mirror', elem.val());
        }

        // 超出范围的样式
        var outOfRange = value < min || value > max;
        elem[outOfRange && !noAction ? 'addClass' : 'removeClass'](
          OUT_OF_RANGE,
        );

        if (isInit) return;

        // 更新按钮状态
        var controlBtn = {
          increment: elem.next().find('.layui-icon-up'),
          decrement: elem.next().find('.layui-icon-down'),
        };
        controlBtn.increment[
          value >= max && !noAction ? 'addClass' : 'removeClass'
        ](DISABLED);
        controlBtn.decrement[
          value <= min && !noAction ? 'addClass' : 'removeClass'
        ](DISABLED);
      };

      // 初始化输入框动态点缀
      elemForm.find('input[lay-affix],textarea[lay-affix]').each(function () {
        var othis = $(this);
        var affix = othis.attr('lay-affix');
        var CLASS_WRAP = 'layui-input-wrap';
        var CLASS_SUFFIX = 'layui-input-suffix';
        var CLASS_AFFIX = 'layui-input-affix';
        var disabled = othis.is('[disabled]') || othis.is('[readonly]');

        // 根据是否空值来显示或隐藏元素
        var showAffix = function (elem, value) {
          elem = $(elem);
          if (!elem[0]) return;
          elem[$.trim(value) ? 'removeClass' : 'addClass'](HIDE);
        };

        // 渲染动态点缀内容
        var renderAffix = function (opts) {
          opts = $.extend(
            {},
            affixOptions[affix] || {
              value: affix,
            },
            opts,
            lay.options(othis[0]),
          );
          var elemAffix = $('<div class="' + CLASS_AFFIX + '">');
          var value = layui.isArray(opts.value) ? opts.value : [opts.value];
          var elemIcon = $(
            (function () {
              var arr = [];
              layui.each(value, function (i, item) {
                arr.push(
                  '<i class="layui-icon layui-icon-' +
                    item +
                    (opts.disabled ? ' ' + DISABLED : '') +
                    '"></i>',
                );
              });
              return arr.join('');
            })(),
          );

          elemAffix.append(elemIcon); // 插入图标元素

          // 追加 className
          if (opts.split) elemAffix.addClass('layui-input-split');
          if (opts.className) elemAffix.addClass(opts.className);

          // 移除旧的元素
          var hasElemAffix = othis.next('.' + CLASS_AFFIX);
          if (hasElemAffix[0]) hasElemAffix.remove();

          // 是否在规定的容器中
          if (!othis.parent().hasClass(CLASS_WRAP)) {
            othis.wrap('<div class="' + CLASS_WRAP + '"></div>');
          }

          // 是否已经存在后缀元素
          var hasElemSuffix = othis.next('.' + CLASS_SUFFIX);
          if (hasElemSuffix[0]) {
            hasElemAffix = hasElemSuffix.find('.' + CLASS_AFFIX);
            if (hasElemAffix[0]) hasElemAffix.remove();

            hasElemSuffix.prepend(elemAffix);

            othis.css('padding-right', function () {
              var paddingRight = othis.closest('.layui-input-group')[0]
                ? 0
                : hasElemSuffix.outerWidth();
              return paddingRight + elemAffix.outerWidth();
            });
          } else {
            elemAffix.addClass(CLASS_SUFFIX);
            othis.after(elemAffix);
          }

          opts.show === 'auto' && showAffix(elemAffix, othis.val());

          typeof opts.init === 'function' && opts.init.call(this, othis, opts);

          // 输入事件
          othis.on('input propertychange', function () {
            var value = this.value;
            opts.show === 'auto' && showAffix(elemAffix, value);
          });

          // 失去焦点事件
          othis.on('blur', function () {
            typeof opts.blur === 'function' &&
              opts.blur.call(this, othis, opts);
          });

          // 点击动态后缀事件
          elemIcon.on('click', function () {
            var inputFilter = othis.attr('lay-filter');
            if ($(this).hasClass(DISABLED)) return;

            typeof opts.click === 'function' &&
              opts.click.call(this, othis, opts);

            // 对外事件
            layui.event.call(
              this,
              MOD_NAME,
              'input-affix(' + inputFilter + ')',
              {
                elem: othis[0],
                affix: affix,
                options: opts,
              },
            );
          });
        };

        // 动态点缀配置项
        var affixOptions = {
          eye: {
            // 密码显隐
            value: 'eye-invisible',
            click: function (elem) {
              // 事件
              var SHOW_NAME = 'LAY_FORM_INPUT_AFFIX_SHOW';
              var isShow = elem.data(SHOW_NAME);

              elem
                .attr('type', isShow ? 'password' : 'text')
                .data(SHOW_NAME, !isShow);

              renderAffix({
                value: isShow ? 'eye-invisible' : 'eye',
              });
            },
          },
          clear: {
            // 内容清除
            value: 'clear',
            click: function (elem) {
              elem.val('').focus();
              showAffix($(this).parent(), null);
            },
            show: 'auto', // 根据输入框值是否存在来显示或隐藏点缀图标
            disabled: disabled, // 跟随输入框禁用状态
          },
          number: {
            // 数字输入框
            value: ['up', 'down'],
            split: true,
            className: 'layui-input-number',
            disabled: othis.is('[disabled]'), // 跟随输入框禁用状态
            init: function (elem) {
              // 旧版浏览器不支持更改 input 元素的 type 属性，需要主动设置 text
              if (elem.attr('type') === 'text' || elem[0].type === 'text') {
                var ns = '.lay_input_number';
                var skipCheck = false;
                var isComposition = false;
                var isReadonly = typeof elem.attr('readonly') === 'string';
                var isMouseWheel = typeof elem.attr('lay-wheel') === 'string';
                var btnElem = elem.next('.layui-input-number').children('i');
                // 旧版浏览器不支持 beforeInput 事件，需要设置一个 attr 存储输入前的值
                elem.attr('lay-input-mirror', elem.val());
                elem.off(ns);
                // 旧版浏览器不支持 event.inputType 属性，需要用 keydown 事件来判断是否跳过输入检查
                elem.on('keydown' + ns, function (e) {
                  skipCheck = false;
                  if (e.keyCode === 8 || e.keyCode === 46) {
                    // Backspace || Delete
                    skipCheck = true;
                  }
                  // Up & Down 键盘事件处理
                  if (
                    !isReadonly &&
                    btnElem.length === 2 &&
                    (e.keyCode === 38 || e.keyCode === 40)
                  ) {
                    e.preventDefault();
                    btnElem.eq(e.keyCode === 38 ? 0 : 1).click();
                  }
                });
                elem.on('input' + ns + ' propertychange' + ns, function (e) {
                  if (
                    isComposition ||
                    (e.type === 'propertychange' &&
                      e.originalEvent.propertyName !== 'value')
                  )
                    return;
                  if (skipCheck || canInputNumber(this.value)) {
                    elem.attr('lay-input-mirror', this.value);
                  } else {
                    // 恢复输入前的值
                    this.value = elem.attr('lay-input-mirror');
                  }
                  elem.toggleClass(BAD_INPUT, isNaN(Number(this.value)));
                });
                elem.on('compositionstart' + ns, function () {
                  isComposition = true;
                });
                elem.on('compositionend' + ns, function () {
                  isComposition = false;
                  elem.trigger('input');
                });
                // 响应鼠标滚轮或触摸板
                if (isMouseWheel) {
                  elem.on(
                    ['wheel', 'mousewheel', 'DOMMouseScroll'].join(ns + ' ') +
                      ns,
                    function (e) {
                      if (!btnElem.length) return;
                      if (!$(this).is(':focus')) return;
                      var direction = 0;
                      e.preventDefault();
                      // IE9+，chrome 和 firefox 同时添加 'wheel' 和 'mousewheel' 事件时，只执行 'wheel' 事件
                      if (e.type === 'wheel') {
                        e.deltaX = e.originalEvent.deltaX;
                        e.deltaY = e.originalEvent.deltaY;
                        direction =
                          Math.abs(e.deltaX) >= Math.abs(e.deltaY)
                            ? e.deltaX
                            : e.deltaY;
                      } else if (e.type === 'mousewheel') {
                        direction = -e.originalEvent.wheelDelta;
                      } else if (e.type === 'DOMMouseScroll') {
                        direction = e.originalEvent.detail;
                      }
                      btnElem.eq(direction > 0 ? 1 : 0).click();
                    },
                  );
                }

                if (isReadonly) {
                  btnElem.addClass(DISABLED);
                }
              }
              handleInputNumber.call(this, elem, 'init');
            },
            click: function (elem) {
              handleInputNumber.call(this, elem, 'click');
            },
            blur: function (elem) {
              handleInputNumber.call(this, elem, 'blur');
            },
          },
        };

        renderAffix();
      });
    },

    // 下拉选择框
    select: function (elem) {
      var TIPS = i18n.$t('form.select.placeholder');
      var CLASS = 'layui-form-select';
      var TITLE = 'layui-select-title';
      var NONE = 'layui-select-none';
      var CREATE_OPTION = 'layui-select-create-option';
      var PANEL_WRAP = 'layui-select-panel-wrap';
      var PANEL_ELEM_DATA = 'layui-select-panel-elem-data';
      var selects = elem || elemForm.find('select');

      // 各种事件
      var events = function (
        reElem,
        titleElem,
        disabled,
        isSearch,
        isCreatable,
        isAppendTo,
      ) {
        var select = $(this);
        var title = titleElem;
        var input = title.find('input');
        var dl = reElem.find('dl');
        // var dds = dl.children('dd');
        var dts = dl.children('dt'); // select 分组dt元素
        var index = this.selectedIndex; // 当前选中的索引
        var initValue = '';
        var removeClickOutsideEvent;

        if (disabled) return;

        /**
         * 搜索项
         * @typedef searchOption
         * @prop {boolean} [caseSensitive=false] 是否区分大小写
         * @prop {boolean} [fuzzy=false] 是否开启模糊匹配，开启后将会忽略模式出现在字符串中的位置。
         */
        /** @type {searchOption} */
        var laySearch =
          select.attr('lay-search') === 'cs'
            ? { caseSensitive: true }
            : lay.options(select, { attr: 'lay-search' });
        // 目前只支持 body
        var appendTarget = select.attr('lay-append-to') || 'body';
        var appendPosition = select.attr('lay-append-position');

        // #1449
        // IE10 和 11 中，带有占位符的 input 元素获得/失去焦点时，会触发 input 事件
        // 当鼠标按下时，根据 input 元素上的 __ieph 标识忽略 input 事件
        var needPlaceholderPatch = !!(
          lay.ie &&
          (lay.ie === '10' || lay.ie === '11') &&
          input.attr('placeholder')
        );

        // 展开下拉
        var showDown = function () {
          if (isAppendTo) {
            // 如果追加面板元素后出现滚动条，触发元素宽度可能会有变化，所以先追加面板元素
            reElem.appendTo(appendTarget).css({ width: title.width() + 'px' });

            var updatePosition = function () {
              lay.position(title[0], reElem[0], {
                position: appendPosition,
                allowBottomOut: true,
                offset: [0, 5],
              });
            };

            updatePosition();
            $(window).on('resize.lay_select_resize', updatePosition);
            if (resizeObserver) {
              resizeObserver.observe(reElem[0], updatePosition);
            }
          }
          var top =
            reElem.offset().top + reElem.outerHeight() + 5 - $win.scrollTop();
          var dlHeight = dl.outerHeight();
          var dds = dl.children('dd');

          index = select[0].selectedIndex; // 获取最新的 selectedIndex
          title.parent().addClass(CLASS + 'ed');
          dds.removeClass(HIDE);
          dts.removeClass(HIDE);

          // 初始选中样式
          dds.removeClass(THIS);
          index >= 0 && dds.eq(index).addClass(THIS);

          // 上下定位识别
          if (top + dlHeight > $win.height() && top >= dlHeight) {
            reElem.addClass(CLASS + 'up');
          }

          followScroll();

          if (needPlaceholderPatch) {
            dl.off('mousedown.lay_select_ieph').on(
              'mousedown.lay_select_ieph',
              function () {
                input[0].__ieph = true;
                setTimeout(function () {
                  input[0].__ieph = false;
                }, 60);
              },
            );
          }

          removeClickOutsideEvent = lay.onClickOutside(
            isAppendTo ? reElem[0] : dl[0],
            function () {
              hideDown();
              initValue && input.val(initValue);
            },
            { ignore: title, detectIframe: true, capture: false },
          );
        };

        // 隐藏下拉
        var hideDown = function (choose) {
          title.parent().removeClass(CLASS + 'ed ' + CLASS + 'up');
          input.blur();
          isCreatable && dl.children('.' + CREATE_OPTION).remove();
          if (typeof removeClickOutsideEvent === 'function') {
            removeClickOutsideEvent();
            removeClickOutsideEvent = null;
          }
          if (isAppendTo) {
            reElem.detach();
            $(window).off('resize.lay_select_resize');
            if (resizeObserver) {
              resizeObserver.unobserve(reElem[0]);
            }
          }

          if (choose) return;

          notOption(input.val(), function (none) {
            var selectedIndex = select[0].selectedIndex;

            // 未查询到相关值
            if (none) {
              initValue = $(select[0].options[selectedIndex]).prop('text'); // 重新获得初始选中值

              // 如果是第一项，且文本值等于 placeholder，则清空初始值
              if (
                selectedIndex === 0 &&
                initValue === input.attr('placeholder')
              ) {
                initValue = '';
              }

              // 如果有选中值，则将输入框纠正为该值。否则清空输入框
              input.val(initValue || '');
            }
          });
        };

        // 定位下拉滚动条
        var followScroll = function () {
          var thisDd = dl.children('dd.' + THIS);

          if (!thisDd[0]) return;

          var posTop = thisDd.position().top;
          var dlHeight = dl.height();
          var ddHeight = thisDd.height();

          // 若选中元素在滚动条不可见底部
          if (posTop > dlHeight) {
            dl.scrollTop(posTop + dl.scrollTop() - dlHeight + ddHeight - 5);
          }

          // 若选择元素在滚动条不可见顶部
          if (posTop < 0) {
            dl.scrollTop(posTop + dl.scrollTop() - 5);
          }
        };

        // 点击标题区域
        title.on('click', function () {
          title.parent().hasClass(CLASS + 'ed') ? hideDown() : showDown();
          dl.find('.' + NONE).remove();
        });

        // 点击箭头获取焦点
        title.find('.layui-edge').on('click', function () {
          input.focus();
        });

        // select 中 input 键盘事件
        input
          .on('keyup', function (e) {
            // 键盘松开
            var keyCode = e.keyCode;

            // Tab键展开
            if (keyCode === 9) {
              showDown();
            }
          })
          .on('keydown', function (e) {
            // 键盘按下
            var keyCode = e.keyCode;

            // Tab键隐藏
            if (keyCode === 9) {
              hideDown();
            }

            // 标注 dd 的选中状态
            var setThisDd = function (prevNext) {
              e.preventDefault();
              var allDisplayedElem = dl.children(
                'dd:not(.' + HIDE + ',.' + DISABLED + ')',
              );
              if (!allDisplayedElem.length) return;
              var firstIndex = 0;
              var lastIndex = allDisplayedElem.length - 1;
              var selectedIndex = -1;

              layui.each(allDisplayedElem, function (index, el) {
                if ($(el).hasClass(THIS)) {
                  selectedIndex = index;
                  return true;
                }
              });

              var nextIndex =
                prevNext === 'prev'
                  ? selectedIndex - 1 < firstIndex
                    ? lastIndex
                    : selectedIndex - 1
                  : selectedIndex + 1 > lastIndex
                    ? firstIndex
                    : selectedIndex + 1;

              var selectedElem = allDisplayedElem.eq(nextIndex);
              selectedElem.addClass(THIS).siblings().removeClass(THIS); // 标注样式
              followScroll(); // 定位滚动条
            };

            if (keyCode === 38) setThisDd('prev'); // Up 键
            if (keyCode === 40) setThisDd('next'); // Down 键

            // Enter 键
            if (keyCode === 13) {
              e.preventDefault();
              dl.children('dd.' + THIS).trigger('click');
            }
          })
          .on('paste', function () {
            showDown();
          });

        // 检测值是否不属于 select 项
        var notOption = function (value, callback, origin) {
          var num = 0;
          var dds = dl.children('dd');
          var hasEquals = false;
          var rawValue = value;
          var fuzzyMatchRE;
          if (!laySearch.caseSensitive) {
            value = value.toLowerCase();
          }
          if (laySearch.fuzzy) {
            fuzzyMatchRE = fuzzyMatchRegExp(value, laySearch.caseSensitive);
          }
          layui.each(dds, function () {
            var othis = $(this);
            var text = othis.text();
            var isCreateOption = isCreatable && othis.hasClass(CREATE_OPTION);

            // 需要区分大小写
            if (isCreatable && !isCreateOption && text === rawValue) {
              hasEquals = true;
            }

            // 是否区分大小写
            if (!laySearch.caseSensitive) {
              text = text.toLowerCase();
            }

            // 匹配
            var not = laySearch.fuzzy
              ? !fuzzyMatchRE.test(text)
              : text.indexOf(value) === -1;

            if (value === '' || origin === 'blur' ? value !== text : not) num++;
            origin === 'keyup' &&
              othis[
                (isCreatable ? not && !isCreateOption : not)
                  ? 'addClass'
                  : 'removeClass'
              ](HIDE);
          });
          // 处理 select 分组元素
          origin === 'keyup' &&
            layui.each(dts, function () {
              var othis = $(this);
              var thisDds = othis.nextUntil('dt').filter('dd'); // 当前分组下的dd元素
              if (isCreatable) thisDds = thisDds.not('.' + CREATE_OPTION);
              var allHide = thisDds.length == thisDds.filter('.' + HIDE).length; // 当前分组下所有dd元素都隐藏了
              othis[allHide ? 'addClass' : 'removeClass'](HIDE);
            });
          var none = num === dds.length;
          return (callback(none, hasEquals), none);
        };

        // 搜索匹配
        var search = function (e) {
          var value = this.value,
            keyCode = e.keyCode;

          if (
            keyCode === 9 ||
            keyCode === 13 ||
            keyCode === 37 ||
            keyCode === 38 ||
            keyCode === 39 ||
            keyCode === 40
          ) {
            return false;
          }

          if (needPlaceholderPatch && e.target.__ieph) {
            e.target.__ieph = false;
            return false;
          }

          notOption(
            value,
            function (none, hasEquals) {
              if (isCreatable) {
                if (hasEquals) {
                  dl.children('.' + CREATE_OPTION).remove();
                } else {
                  var createOptionElem = dl.children('.' + CREATE_OPTION);
                  if (createOptionElem[0]) {
                    createOptionElem.attr('lay-value', value).text(value);
                  } else {
                    // 临时显示在顶部
                    var ddElem = $('<dd>')
                      .addClass(CREATE_OPTION)
                      .attr('lay-value', value)
                      .text(value);
                    var firstOptionELem = dl.children().eq(0);
                    var hasTips = firstOptionELem.hasClass('layui-select-tips');
                    firstOptionELem[hasTips ? 'after' : 'before'](ddElem);
                  }
                }
              } else {
                if (none) {
                  dl.find('.' + NONE)[0] ||
                    dl.append(
                      '<p class="' +
                        NONE +
                        '">' +
                        i18n.$t('form.select.noMatch') +
                        '</p>',
                    );
                } else {
                  dl.find('.' + NONE).remove();
                }
              }
            },
            'keyup',
          );

          // 当搜索值清空时
          if (value === '') {
            // 取消选中项
            select.val('');
            dl.find('.' + THIS).removeClass(THIS);
            (select[0].options[0] || {}).value ||
              dl.children('dd:eq(0)').addClass(THIS);
            dl.find('.' + NONE).remove();
            isCreatable && dl.children('.' + CREATE_OPTION).remove();
          }

          followScroll(); // 定位滚动条
        };

        if (isSearch) {
          input
            .on('input propertychange', layui.debounce(search, 50))
            .on('blur', function () {
              var selectedIndex = select[0].selectedIndex;

              initValue = $(select[0].options[selectedIndex]).prop('text'); // 重新获得初始选中值

              // 如果是第一项，且文本值等于 placeholder，则清空初始值
              if (
                selectedIndex === 0 &&
                initValue === input.attr('placeholder')
              ) {
                initValue = '';
              }

              setTimeout(function () {
                notOption(
                  input.val(),
                  function () {
                    initValue || input.val(''); // none && !initValue
                  },
                  'blur',
                );
              }, 200);
            });
        }

        // 选择
        dl.on('click', 'dd', function () {
          var othis = $(this),
            value = othis.attr('lay-value');
          var filter = select.attr('lay-filter'); // 获取过滤器

          if (othis.hasClass(DISABLED)) return false;

          // 将新增的 option 元素添加到末尾
          if (isCreatable && othis.hasClass(CREATE_OPTION)) {
            var optionElem = $('<option>').text(othis.text());
            var displayValue = optionElem.prop('text');
            value = displayValue;
            optionElem.attr('value', displayValue);
            select.append(optionElem);
            othis
              .removeClass(CREATE_OPTION)
              .attr('lay-value', displayValue)
              .text(displayValue);
            dl.append(othis);
          }

          if (othis.hasClass('layui-select-tips')) {
            input.val('');
          } else {
            input.val(othis.text());
            othis.addClass(THIS);
          }

          othis.siblings().removeClass(THIS);
          select.val(value).removeClass('layui-form-danger');

          layui.event.call(this, MOD_NAME, 'select(' + filter + ')', {
            elem: select[0],
            value: value,
            othis: reElem,
          });

          hideDown(true);
          return false;
        });

        // 用于开启 lay-append-to 时兼容 dropdown
        dl.on('mousedown pointerdown touchstart', function (e) {
          layui.stope(e);
        });

        reElem.find('dl>dt').on('click', function () {
          return false;
        });

        if (isAppendTo) {
          titleElem.on('_lay-select-destroy', function () {
            reElem.remove();
          });
        }
      };

      // 仅 appendTo 使用，移除触发元素时，自动移除面板元素
      $.event.special['_lay-select-destroy'] = {
        remove: function (handleObj) {
          handleObj.handler();
        },
      };

      // 初始渲染 select 组件选项
      selects.each(function (index, select) {
        var othis = $(this);
        var hasRender = othis.next('.' + CLASS);
        var disabled = this.disabled;
        var value = select.value;
        var selected = $(select.options[select.selectedIndex]); // 获取当前选中项
        var optionsFirst = select.options[0];

        // 为忽略渲染的 select 元素保持原生显示状态
        if (othis.closest('[lay-ignore]').length) {
          return othis.show();
        }

        var isSearch = typeof othis.attr('lay-search') === 'string';
        var isCreatable =
          typeof othis.attr('lay-creatable') === 'string' && isSearch;
        var isAppendTo = typeof othis.attr('lay-append-to') === 'string';
        var placeholder = optionsFirst
          ? optionsFirst.value
            ? TIPS
            : optionsFirst.text || TIPS
          : TIPS;

        // 用于替代 select 的外层容器
        var selectWrapper = (function () {
          var elem = $('<div class="' + CLASS + '"></div>');
          if (!isSearch) {
            elem.addClass('layui-unselect');
          }
          if (disabled) {
            elem.addClass('layui-select-disabled');
          }
          return elem;
        })();

        var inputElem = (function () {
          var elem = $('<input type="text" class="layui-input">');

          // 设置占位符和默认值
          elem.prop('placeholder', placeholder);
          elem.val(value ? selected.prop('text') : '');

          // 设置未开启搜索或禁用时的输入框只读状态
          if (!isSearch || disabled) {
            elem.prop('readonly', true);
          }

          // 添加禁用状态时的 className
          if (disabled) {
            elem.addClass(DISABLED);
          }

          return elem;
        })();

        var titleElem = (function () {
          var elem = $('<div class="' + TITLE + '"></div>');
          elem.append(inputElem);
          elem.append('<i class="layui-edge"></i>');
          return elem;
        })();

        var contentElem = (function () {
          var elem = $('<dl class="layui-anim layui-anim-upbit"></dl>');
          if (othis.find('optgroup')[0]) {
            elem.addClass('layui-select-group');
          }
          var content = (function () {
            var arr = [];
            layui.each(othis.find('optgroup,option'), function (index, item) {
              var tagName = item.tagName.toLowerCase();
              var dd = $('<dd lay-value=""></dd>');
              if (index === 0 && !item.value && tagName !== 'optgroup') {
                dd.addClass('layui-select-tips');
                dd.text(item.text || TIPS);
                arr.push(dd.prop('outerHTML'));
              } else if (tagName === 'optgroup') {
                var dt = $('<dt></dt>');
                dt.text(item.label);
                arr.push(dt.prop('outerHTML'));
              } else {
                dd.attr('lay-value', item.value);
                if (value === item.value) {
                  dd.addClass(THIS);
                }
                if (item.disabled) {
                  dd.addClass(DISABLED);
                }
                dd.text(item.text);
                arr.push(dd.prop('outerHTML'));
              }
            });
            if (arr.length === 0) {
              arr.push(
                '<dd lay-value="" class="' +
                  DISABLED +
                  '">' +
                  i18n.$t('form.select.noData') +
                  '</dd>',
              );
            }
            return arr.join('');
          })();
          elem.html(content);
          return elem;
        })();

        // 如果已经渲染，则 Rerender
        if (hasRender[0]) {
          if (isAppendTo) {
            var panelWrapElem = hasRender.data(PANEL_ELEM_DATA);
            panelWrapElem && panelWrapElem.remove();
          }
          hasRender.remove();
        }
        if (isAppendTo) {
          selectWrapper.append(titleElem);
          othis.after(selectWrapper);
          var contentWrapElem = $(
            '<div class="' + CLASS + ' ' + PANEL_WRAP + '"></div>',
          ).append(contentElem);
          selectWrapper.data(PANEL_ELEM_DATA, contentWrapElem); // 将面板元素对象记录在触发元素 data 中，重新渲染时需要清理旧面板元素
          events.call(
            this,
            contentWrapElem,
            titleElem,
            disabled,
            isSearch,
            isCreatable,
            isAppendTo,
          );
        } else {
          selectWrapper.append(titleElem).append(contentElem);
          othis.after(selectWrapper);
          events.call(
            this,
            selectWrapper,
            titleElem,
            disabled,
            isSearch,
            isCreatable,
            isAppendTo,
          );
        }
      });
    },

    // 复选框/开关
    checkbox: function (elem) {
      var CLASS = {
        checkbox: ['layui-form-checkbox', 'layui-form-checked', 'checkbox'],
        switch: ['layui-form-switch', 'layui-form-onswitch', 'switch'],
        SUBTRA: 'layui-icon-indeterminate',
        ICON: 'layui-icon',
        ICON_OK: 'layui-icon-ok',
      };
      var clickEventName = 'click.lay_checkbox_click';
      var checks = elem || elemForm.find('input[type=checkbox]');
      // 风格
      /* var skins = {
        primary: true, // 默认风格
        tag: true, // 标签风格
        switch: true, // 开关风格
      }; */
      // 事件
      var events = function (reElem, RE_CLASS) {
        var check = $(this);
        var skin = check.attr('lay-skin') || 'primary';
        var isSwitch = skin === 'switch';
        var isPrimary = skin === 'primary';

        // 勾选
        // 通过重新赋值触发美化元素样式更新
        check.off(clickEventName).on(clickEventName, function () {
          var filter = check.attr('lay-filter'); // 获取过滤器
          var checked = check[0].checked;
          var indeterminate = check[0].indeterminate;

          // 禁用
          if (check[0].disabled) return;

          // 半选
          check[0].indeterminate = indeterminate;

          // 开关
          check[0].checked = checked;

          // 事件
          layui.event.call(
            check[0],
            MOD_NAME,
            RE_CLASS[2] + '(' + filter + ')',
            {
              elem: check[0],
              value: check[0].value,
              othis: reElem,
            },
          );
        });

        reElem.on('click', function () {
          var hasLabel = check.closest('label').length;
          if (!hasLabel) {
            check.trigger('click');
          }
        });

        that.syncAppearanceOnPropChanged(this, 'checked', function () {
          if (isSwitch) {
            var title = (
              reElem.next('*[lay-checkbox]')[0]
                ? reElem.next().html()
                : check.attr('title') || ''
            ).split('|');
            reElem
              .children('div')
              .html(this.checked ? title[0] : title[1] || title[0]);
          }
          reElem.toggleClass(RE_CLASS[1], this.checked);
        });

        if (isPrimary) {
          that.syncAppearanceOnPropChanged(this, 'indeterminate', function () {
            if (this.indeterminate) {
              reElem
                .children('.' + CLASS.ICON)
                .removeClass(CLASS.ICON_OK)
                .addClass(CLASS.SUBTRA);
            } else {
              reElem
                .children('.' + CLASS.ICON)
                .removeClass(CLASS.SUBTRA)
                .addClass(CLASS.ICON_OK);
            }
          });
        }
      };

      // 遍历复选框
      checks.each(function (index, check) {
        var othis = $(this);
        var skin = othis.attr('lay-skin') || 'primary';
        var title = util.escape(
          $.trim(
            check.title ||
              (function () {
                // 向下兼容 lay-text 属性
                return (check.title = othis.attr('lay-text') || '');
              })(),
          ),
        );
        var disabled = this.disabled;

        // if(!skins[skin]) skin = 'primary'; // 若非内置风格，则强制为默认风格
        var RE_CLASS = CLASS[skin] || CLASS.checkbox;

        // 替代元素
        var hasRender = othis.next('.' + RE_CLASS[0]);
        hasRender[0] && hasRender.remove(); // 若已经渲染，则 Rerender

        // 若存在标题模板，则优先读取标题模板
        var titleTplAttrs = [];
        if (othis.next('[lay-checkbox]')[0]) {
          var titleTplElem = othis.next();
          title = titleTplElem.html() || '';
          if (titleTplElem[0].attributes.length > 1) {
            layui.each(titleTplElem[0].attributes, function (i, attr) {
              if (attr.name !== 'lay-checkbox') {
                titleTplAttrs.push(attr.name + '="' + attr.value + '"');
              }
            });
          }
        }
        titleTplAttrs = titleTplAttrs.join(' ');

        // 若为开关，则对 title 进行分隔解析
        title = skin === 'switch' ? title.split('|') : [title];

        if (othis.closest('[lay-ignore]').length) return othis.show();

        // 处理 IE8 indeterminate 属性重新定义 get set 后无法设置值的问题
        if (needCheckboxFallback) {
          toggleAttribute.call(check, 'lay-form-sync-checked', check.checked);
          !check.checked &&
            toggleAttribute.call(
              check,
              'lay-form-sync-indeterminate',
              check.indeterminate,
            );
        }

        // 替代元素
        var reElem = $(
          [
            '<div class="layui-unselect ' + RE_CLASS[0],
            check.checked ? ' ' + RE_CLASS[1] : '', // 选中状态
            disabled ? ' layui-checkbox-disabled ' + DISABLED : '', // 禁用状态
            '"',
            skin ? ' lay-skin="' + skin + '"' : '', // 风格
            '>',
            (function () {
              // 不同风格的内容
              var type = {
                // 复选框
                checkbox: [
                  title[0]
                    ? '<div ' + titleTplAttrs + '>' + title[0] + '</div>'
                    : skin === 'primary'
                      ? ''
                      : '<div></div>',
                  '<i class="layui-icon ' +
                    (skin === 'primary' &&
                    !check.checked &&
                    othis.get(0).indeterminate
                      ? CLASS.SUBTRA
                      : 'layui-icon-ok') +
                    '"></i>',
                ].join(''),
                // 开关
                switch:
                  '<div>' +
                  ((check.checked ? title[0] : title[1] || title[0]) || '') +
                  '</div><i></i>',
              };
              return type[skin] || type['checkbox'];
            })(),
            '</div>',
          ].join(''),
        );

        othis.after(reElem);
        events.call(this, reElem, RE_CLASS);
      });
    },

    // 单选框
    radio: function (elem) {
      var CLASS = 'layui-form-radio';
      var ICON = ['layui-icon-radio', 'layui-icon-circle'];
      var radios = elem || elemForm.find('input[type=radio]');
      var clickEventName = 'click.lay_radio_click';

      // 事件
      var events = function (reElem) {
        var radio = $(this);
        var ANIM = 'layui-anim-scaleSpring';

        radio.off(clickEventName).on(clickEventName, function () {
          var filter = radio.attr('lay-filter'); // 获取过滤器

          if (radio[0].disabled) return;

          radio[0].checked = true;

          layui.event.call(radio[0], MOD_NAME, 'radio(' + filter + ')', {
            elem: radio[0],
            value: radio[0].value,
            othis: reElem,
          });
        });

        reElem.on('click', function () {
          var hasLabel = radio.closest('label').length;
          if (!hasLabel) {
            radio.trigger('click');
          }
        });

        that.syncAppearanceOnPropChanged(this, 'checked', function () {
          var radioEl = this;
          if (radioEl.checked) {
            reElem.addClass(CLASS + 'ed');
            reElem.children('.layui-icon').addClass(ANIM + ' ' + ICON[0]);
            var forms = radio.parents(ELEM);
            var sameRadios = forms.find(
              'input[name=' +
                radioEl.name.replace(/(\.|#|\[|\])/g, '\\$1') +
                ']',
            ); // 找到相同name的兄弟
            layui.each(sameRadios, function () {
              if (radioEl === this) return;
              this.checked = false;
            });
          } else {
            reElem.removeClass(CLASS + 'ed');
            reElem
              .children('.layui-icon')
              .removeClass(ANIM + ' ' + ICON[0])
              .addClass(ICON[1]);
          }
        });
      };

      // 初始渲染
      radios.each(function (index, radio) {
        var othis = $(this),
          hasRender = othis.next('.' + CLASS);
        var disabled = this.disabled;
        var skin = othis.attr('lay-skin');

        if (othis.closest('[lay-ignore]').length) return othis.show();

        if (needCheckboxFallback) {
          toggleAttribute.call(radio, 'lay-form-sync-checked', radio.checked);
        }

        hasRender[0] && hasRender.remove(); // 如果已经渲染，则Rerender

        var title = util.escape(radio.title || '');
        var titleTplAttrs = [];
        if (othis.next('[lay-radio]')[0]) {
          var titleTplElem = othis.next();
          title = titleTplElem.html() || '';
          if (titleTplElem[0].attributes.length > 1) {
            layui.each(titleTplElem[0].attributes, function (i, attr) {
              if (attr.name !== 'lay-radio') {
                titleTplAttrs.push(attr.name + '="' + attr.value + '"');
              }
            });
          }
        }
        titleTplAttrs = titleTplAttrs.join(' ');

        // 替代元素
        var reElem = $(
          [
            '<div class="layui-unselect ' + CLASS,
            radio.checked ? ' ' + CLASS + 'ed' : '', // 选中状态
            (disabled ? ' layui-radio-disabled ' + DISABLED : '') + '"', // 禁用状态
            skin ? ' lay-skin="' + skin + '"' : '',
            '>',
            '<i class="layui-anim layui-icon ' +
              ICON[radio.checked ? 0 : 1] +
              '"></i>',
            '<div ' + titleTplAttrs + '>' + title + '</div>',
            '</div>',
          ].join(''),
        );

        othis.after(reElem);
        events.call(this, reElem);
      });
    },
  };

  // 执行所有渲染项
  var renderItem = function () {
    layui.each(items, function (index, item) {
      item();
    });
  };

  // jquery 对象
  if (layui.type(type) === 'object') {
    // 若对象为表单域容器
    if ($(type).is(ELEM)) {
      elemForm = $(type);
      renderItem();
    } else {
      // 对象为表单项
      type.each(function (index, item) {
        var elem = $(item);
        if (!elem.closest(ELEM).length) {
          return; // 若不在 layui-form 容器中直接跳过
        }
        if (item.tagName === 'SELECT') {
          items['select'](elem);
        } else if (item.tagName === 'INPUT') {
          var itemType = item.type;
          if (itemType === 'checkbox' || itemType === 'radio') {
            items[itemType](elem);
          } else {
            items['input'](elem);
          }
        }
      });
    }
  } else {
    type
      ? items[type]
        ? items[type]()
        : hint.error(
            '[form] "' + type + '" is an unsupported form element type',
          )
      : renderItem();
  }
  return that;
};

/**
 * checkbox 和 radio 指定属性变化时自动更新 UI
 * 只能用于 boolean 属性
 * @param {HTMLInputElement} elem - HTMLInput 元素
 * @param {'checked' | 'indeterminate'} propName - 属性名
 * @param {() => void} handler - 属性值改变时执行的回调
 * @see https://learn.microsoft.com/zh-cn/previous-versions//ff382725(v=vs.85)?redirectedfrom=MSDN
 */
Form.prototype.syncAppearanceOnPropChanged = (function () {
  // 处理 IE8 indeterminate 属性重新定义 get set 后无法设置值的问题
  // 此处性能敏感，不希望每次赋值取值时都判断是否需要 fallback
  if (needCheckboxFallback) {
    return function (elem, propName, handler) {
      var originProps = Object.getOwnPropertyDescriptor(
        HTMLInputElement.prototype,
        propName,
      );

      Object.defineProperty(
        elem,
        propName,
        lay.extend({}, originProps, {
          // 此处的 get 是为了兼容 IE<9
          get: function () {
            return (
              typeof this.getAttribute('lay-form-sync-' + propName) === 'string'
            );
          },
          set: function (newValue) {
            toggleAttribute.call(this, 'lay-form-sync-' + propName, newValue);
            handler.call(this);
          },
        }),
      );
    };
  }
  return function (elem, propName, handler) {
    var originProps = Object.getOwnPropertyDescriptor(
      HTMLInputElement.prototype,
      propName,
    );

    Object.defineProperty(
      elem,
      propName,
      lay.extend({}, originProps, {
        // 此处的 get 是为了兼容 IE<9
        get: function () {
          return originProps.get.call(this);
        },
        set: function (newValue) {
          originProps.set.call(this, newValue);
          handler.call(this);
        },
      }),
    );
  };
})();

/**
 * 主动触发验证
 * @param  {(string|HTMLElement|JQuery)} elem - 要验证的区域表单元素
 * @return {boolean} 返回结果。若验证通过，返回 `true`, 否则返回 `false`
 */
Form.prototype.validate = function (elem) {
  var that = this;
  var intercept; // 拦截标识
  var options = that.config; // 获取全局配置项
  var verify = options.verify; // 验证规则
  var DANGER = 'layui-form-danger'; // 警示样式

  elem = $(elem);

  // 节点不存在可视为 true
  if (!elem[0]) return !0;

  // 若节点不存在特定属性，则查找容器内有待验证的子节点
  if (elem.attr('lay-verify') === undefined) {
    // 若校验的是一个不带验证规则的容器，校验内部的 lay-verify 节点
    if (that.validate(elem.find('*[lay-verify]')) === false) {
      return false;
    }
  }

  // 开始校验
  layui.each(elem, function (_, item) {
    var othis = $(this);
    var verifyStr = othis.attr('lay-verify') || '';
    var vers = verifyStr.split('|');
    var verType = othis.attr('lay-vertype'); // 提示方式
    var value = othis.val();
    value = typeof value === 'string' ? $.trim(value) : value;

    othis.removeClass(DANGER); // 移除警示样式

    // 遍历元素绑定的验证规则
    layui.each(vers, function (_, thisVer) {
      var verst; // 校验结果
      var errorText = ''; // 错误提示文本
      var rule = verify[thisVer]; // 获取校验规则

      // 匹配验证规则
      if (rule) {
        verst =
          typeof rule === 'function'
            ? (errorText = rule(value, item))
            : !rule[0].test(value); // 兼容早期数组中的正则写法

        // 是否属于美化替换后的表单元素
        var isForm2Elem =
          item.tagName.toLowerCase() === 'select' ||
          /^(checkbox|radio)$/.test(item.type);

        errorText = errorText || rule[1];

        // 获取自定义必填项提示文本
        if (thisVer === 'required') {
          errorText = othis.attr('lay-reqtext') || errorText;
        }

        // 若命中校验规则
        if (verst) {
          // 提示层风格
          if (verType === 'tips') {
            layer.tips(
              errorText,
              (function () {
                if (!othis.closest('[lay-ignore]').length) {
                  if (isForm2Elem) {
                    return othis.next();
                  }
                }
                return othis;
              })(),
              { tips: 1 },
            );
          } else if (verType === 'alert') {
            layer.alert(errorText, {
              title: i18n.$t('form.verifyErrorPromptTitle'),
              shadeClose: true,
            });
          }
          // 若返回的为字符或数字，则自动弹出默认提示框；否则由 verify 方法中处理提示
          else if (/\b(string|number)\b/.test(typeof errorText)) {
            layer.msg(errorText, { icon: 5, shift: 6 });
          }

          setTimeout(function () {
            (isForm2Elem ? othis.next().find('input') : item).focus();
          }, 7);

          othis.addClass(DANGER);
          return (intercept = true);
        }
      }
    });

    if (intercept) return intercept;
  });

  return !intercept;
};

// 提交表单并校验
var submit = (Form.prototype.submit = function (filter, callback) {
  var field = {}; // 字段集合
  var button = $(this); // 当前触发的按钮

  // 表单域 lay-filter 属性值
  var layFilter =
    typeof filter === 'string' ? filter : button.attr('lay-filter');

  // 当前所在表单域
  var elem = this.getFormElem
    ? this.getFormElem(layFilter)
    : button.parents(ELEM).eq(0);

  // 获取需要校验的元素
  var verifyElem = elem.find('*[lay-verify]');

  // 开始校验
  if (!form.validate(verifyElem)) return false;

  // 获取当前表单值
  field = form.getValue(null, elem);

  // 返回的参数
  var params = {
    elem: this.getFormElem ? window.event && window.event.target : this, // 触发事件的对象
    form: this.getFormElem ? elem[0] : button.parents('form')[0], // 当前所在的 form 元素，如果存在的话
    field: field, // 当前表单数据
  };

  // 回调
  typeof callback === 'function' && callback(params);

  // 事件
  return layui.event.call(this, MOD_NAME, 'submit(' + layFilter + ')', params);
});

function fuzzyMatchRegExp(keyword, caseSensitive) {
  var wordMap = {};
  var regexPattern = ['^'];
  var escapeRegExp = function (str) {
    return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  };

  if (!caseSensitive) keyword = keyword.toLowerCase();

  var i;

  // 统计关键字中各字符出现次数
  var wordArr = keyword.trim().split('');
  for (i = 0; i < wordArr.length; i++) {
    var c = wordArr[i];
    wordMap[c] = (wordMap[c] || 0) + 1;
  }

  // 构建正则表达式模式
  for (c in wordMap) {
    regexPattern.push('(?=.*');
    for (i = 0; i < wordMap[c]; i++) {
      regexPattern.push(escapeRegExp(c));
      if (i !== wordMap[c] - 1) {
        regexPattern.push('.*'); // 在字符之间添加任意字符匹配
      }
    }
    regexPattern.push(')');
  }
  regexPattern.push('.*');

  return new RegExp(regexPattern.join(''), !caseSensitive ? 'i' : undefined);
}

// 引用自 https://github.com/msn0/mdn-polyfills/blob/master/src/Element.prototype.toggleAttribute/toggleattribute.js
function toggleAttribute(name, force) {
  var forcePassed = arguments.length === 2;
  var forceOn = !!force;
  var forceOff = forcePassed && !force;

  if (this.getAttribute(name) !== null) {
    if (forceOn) return true;

    this.removeAttribute(name);
    return false;
  } else {
    if (forceOff) return false;

    this.setAttribute(name, '');
    return true;
  }
}

// 修改自 https://github.com/Tencent/tdesign-common/blob/53786c58752401e648cc45918f2a4dbb9e8cecfa/js/input-number/number.ts#L209
var specialCode = ['-', '.', 'e', 'E', '+'];
function canInputNumber(number) {
  if (number === '') return true;
  // 数字最前方不允许出现连续的两个 0
  if (number.slice(0, 2) === '00') return false;
  // 不能出现空格
  if (number.match(/\s/g)) return false;
  // 只能出现一个点（.）
  var tempMatched = number.match(/\./g);
  if (tempMatched && tempMatched.length > 1) return false;
  // 只能出现一个e（e）
  tempMatched = number.match(/e/g);
  if (tempMatched && tempMatched.length > 1) return false;
  // 只能出现一个负号（-）或 一个正号（+），并且在第一个位置；但允许 3e+10 这种形式
  var tempNumber = number.slice(1);
  tempMatched = tempNumber.match(/(\+|-)/g);
  if (tempMatched && (!/e(\+|-)/i.test(tempNumber) || tempMatched.length > 1))
    return false;
  // 允许输入数字字符
  var isNumber = !isNaN(Number(number));
  if (!isNumber && !(specialCode.indexOf(number.slice(-1)) !== -1))
    return false;
  if (/e/i.test(number) && (!/\de/i.test(number) || /e\./.test(number)))
    return false;
  return true;
}

var form = new Form();
var $dom = $(document);
var $win = $(window);

// 初始自动完成渲染
$(function () {
  form.render();
});

// 表单 reset 重置渲染
$dom.on('reset', ELEM, function () {
  var filter = $(this).attr('lay-filter');
  setTimeout(function () {
    form.render(null, filter);
  }, 50);
});

// 表单提交事件
$dom.on('submit', ELEM, submit).on('click', '*[lay-submit]', submit);

export { form };
